package com.sgy.jar_package;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;

import com.google.common.io.Files;
import com.sgy.utils.FileUtils;
import com.sgy.utils.JarUtils;
import com.sgy.utils.OSInfo;
import com.sgy.utils.UrlUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author shenguangyang
 * @date 2021-12-05 7:53
 */
public abstract class JarPackage {
    private static final Map<String, Object> libExecCache = new ConcurrentHashMap<>();
    private static final String EMPTY = "";

    /*
     * 存放库的包路径
     * 格式为 com/simplifydev/lib/hik/linux64/libhpr.so  或者
     *       com\simplifydev\lib\hik\linux64\hpr.dll
     */
    // private static final List<String> libFileNames = new ArrayList<>();

    /**
     * 获取lib后缀
     */
//    private static String libSuffix() {
//        if (OSInfo.isWindows()) {
//            return ".dll";
//        } else if (OSInfo.isLinux() || OSInfo.isMacOS()) {
//            return ".so";
//        }
//        return ".dll";
//    }
//
//    /**
//     * 获取lib前缀
//     */
//    private static String libPre() {
//        if (OSInfo.isWindows()) {
//            return "";
//        } else if (OSInfo.isLinux() || OSInfo.isMacOS()) {
//            return "lib";
//        }
//        return "";
//    }

    public static synchronized <T extends JarPackage> String getPath(JarPackageInfo libInfo, String targetRootPathOfCustom) {
        try {
            String libPath = copy(libInfo, targetRootPathOfCustom) ;
            System.out.println("init lib: " + libPath);
            return URLDecoder.decode(libPath, "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

//    /**
//     * @param libPackagePath 库的包路径 eg: com/simplifydev/lib/hik/linux64/libhpr.so
//     */
//    private static void addLibPackagePath(String libPackagePath) {
//        libFileNames.add(libPackagePath);
//    }

//    private static <T extends NativeLibPath> String getLibFileName(Class<T> libName) {
//        String name = libName.getSimpleName().toLowerCase();
//        for (String libFileName : libFileNames) {
//            String targetName = libPre() + name + libSuffix();
//            if (libFileName.toLowerCase().contains(targetName)) {
//                return libFileName;
//            }
//        }
//        libFileNames.clear();
//        return "error";
//    }

    /**
     * 执行解析
     * 返回库路径的根路径
     * @param targetRootPathOfCustom 目标路径, 如果没有指定就采用默认生成的路径 ===> $USER_HOME/jar包名称/包路径
     */
    private static synchronized String copy(JarPackageInfo libInfo, String targetRootPathOfCustom) throws Exception {
        targetRootPathOfCustom = StringUtils.isEmpty(targetRootPathOfCustom) ? null : UrlUtils.removeEndSlash(targetRootPathOfCustom);
        String packageMark = libInfo.getPackageMarkClass().getName();
        String packageName = "";
        String jarName =  libInfo.getJarFileName();
        String packageRootPath = "";

        // packageName： com.test1
        // packageRootPath: com/test1 或者 com\test1
        packageName = packageMark.substring(0, packageMark.lastIndexOf("."));
        packageRootPath = packageName.replace(".", File.separator);
        String targetRootPath = JarPackageConstant.LIB_CACHE_PATH + File.separator + jarName.replace(".jar", "") + File.separator + packageRootPath;

        Object value = libExecCache.get(jarName);
        // != null 说明已经执行过了, 直接返回
        if (value != null) {
            return targetRootPath;
        }

        // 非jar包中运行
        if (!JarUtils.isRunningInJar()) {
            Class<?> aClass = Class.forName(packageMark);
            URL url = aClass.getResource("");
            if (url == null) {
                throw new RuntimeException("aClass.getResource(\"\") == null");
            }
            String path = url.getPath();
            path = path.replace("/", File.separator);
            if (OSInfo.isWindows()) {
                path = path.substring(1);
            }
            List<File> allFile = FileUtil.loopFiles(path);

            for (File srcFile : allFile) {
                String filePath = srcFile.getPath();
                // libPath是相对路径
                String targetFilePath = filePath.replace(path, "").replace(packageRootPath, "");
                // 绝对路径
                String targetAbsolutePath = targetRootPath + File.separator + targetFilePath;
                String prePath = targetAbsolutePath.substring(0, targetAbsolutePath.lastIndexOf(File.separator));

                if (StrUtil.isNotEmpty(prePath)) {
                    File preDir = new File(prePath);
                    if (!preDir.exists()) {
                        if (!preDir.mkdirs()) {
                            throw new RuntimeException("create dir fail: dirPath: " + preDir.getPath());
                        }
                    }
                }

                // addLibPackagePath(libPath);
                File targetFile = new File(targetAbsolutePath);

                // 如果用户指定了目标路径, 则拷贝到目标路径上
                if (StringUtils.isEmpty(targetRootPathOfCustom)) {
                    Files.copy(srcFile, targetFile);
                } else {
                    File target = new File(targetRootPathOfCustom +  File.separator + targetFilePath);
                    File parentFile = target.getParentFile();
                    if (parentFile != null && !parentFile.exists()) {
                        parentFile.mkdirs();
                    }
                    Files.copy(srcFile, target);
                }
            }
        } else { // jar包中运行
            String tmpJarPath = JarPackageConstant.LIB_CACHE_PATH + File.separator + jarName;
            String loaderPath = System.getProperty("loader.path");
            if (loaderPath == null) {
                String currentJarPath = JarUtils.getCurrentJarPath();
                JarFile jarFile = new JarFile(currentJarPath);
                Enumeration<JarEntry> entries = jarFile.entries();
                while (entries.hasMoreElements()) {
                    JarEntry entry = entries.nextElement();
                    String entryName = entry.getName();
                    if (entryName.contains("BOOT-INF/lib/" + jarName)) {
                        // 将BOOT-INF/lib/jarName jar包拷贝到本地上
                        InputStream inputStream = jarFile.getInputStream(entry);
                        File file = new File(tmpJarPath);
                        writeFile(inputStream, file);
                        // 从jar中提取文件
                        extractFileFromJarFile(tmpJarPath, packageRootPath, targetRootPath, targetRootPathOfCustom);
                        FileUtils.deleteFile(tmpJarPath);
                        break;
                    }
                }
            } else {
                // 将所有lib单独提出来了
                loaderPath = loaderPath.replace("\\", File.separator);
                File fromFile = new File(loaderPath + File.separator + jarName);
                File toFile = new File(tmpJarPath);
                Files.copy(fromFile, toFile);

                extractFileFromJarFile(tmpJarPath, packageRootPath, targetRootPath,  targetRootPathOfCustom);
            }

        }
        libExecCache.put(jarName, EMPTY);
        return StringUtils.isEmpty(targetRootPathOfCustom) ? targetRootPath : targetRootPathOfCustom;
    }


    /**
     * 从jar中提取 packageRootPath 包下的文件
     * @param jarPath jar包文件路径
     * @param targetRootPath 存放 packageRootPath 包下的文件根路径
     * @param packageRootPath 提取的包
     * @param targetRootPath 用户自定义的文件拷贝的目标路径
     */
    private static void extractFileFromJarFile(String jarPath, String packageRootPath, String targetRootPath,  String targetRootPathOfCustom) throws Exception {
        JarFile targetJarFile = new JarFile(jarPath);
        Enumeration<JarEntry> targetEntries = targetJarFile.entries();
        while (targetEntries.hasMoreElements()) {
            JarEntry targetEntry = targetEntries.nextElement();
            String targetEntryName = targetEntry.getName().replace("/", File.separator);
            String targetFilePath = targetEntryName.replace(packageRootPath + File.separator, "");
            String targetFileAbsolutePath = targetRootPath + File.separator + targetFilePath;

            // 排除 非文件包 等路径
            if (!targetEntryName.contains(packageRootPath) ||
                    (targetEntryName.lastIndexOf(File.separator) == (targetEntryName.length() -1))) {
                continue;
            }

            // 第三段目的为了解析jar包中的文件路径
            String targetFileDir = targetFileAbsolutePath.substring(0, targetFileAbsolutePath.lastIndexOf(File.separator));
            File targetDir = new File(targetFileDir);
            if (!targetDir.exists()) {
                if (!targetDir.mkdirs()) {
                    throw new RuntimeException("create dir fail for target libDir " + targetDir.getPath());
                }
            }
            // addLibPackagePath(libPath);

            InputStream srcInputStream = targetJarFile.getInputStream(targetEntry);
            File targetFile = new File(targetFileAbsolutePath);
            // 如果用户指定了目标路径, 则拷贝到目标路径上
            if (StringUtils.isEmpty(targetRootPathOfCustom)) {
                writeFile(srcInputStream, targetFile);
            } else {
                File target = new File(targetRootPathOfCustom + targetFilePath);
                File parentFile = target.getParentFile();
                if (parentFile != null && !parentFile.exists()) {
                    parentFile.mkdirs();
                }
                writeFile(srcInputStream, target);
            }
        }
    }

    private static void writeFile(InputStream is, File file) throws Exception{
        if(file != null){
            //推荐使用字节流读取，因为虽然读取的是文件，如果是 .exe, .c 这种文件，用字符流读取会有乱码
            try ( OutputStream os = new BufferedOutputStream(java.nio.file.Files.newOutputStream(file.toPath()))) {
                //这里用小数组读取，使用file.length()来一次性读取可能会出错（亲身试验）
                byte[] bytes = new byte[2048 * 1024];
                int len;
                while((len = is.read(bytes)) != -1) {
                    os.write(bytes, 0, len);
                }

            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }
}
